/*MyPhotoIndex Free Photo Organizer <www.myphotoindex.com>
  Copyright (C) <2006-2009>  <Asaf Yarkoni asaf@yarkoni.net>

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using MyPhotoIndex.ImageBank;
using MyPhotoIndex.Interfaces;
using MyPhotoIndex.Interfaces.Imaging;
using MyPhotoIndex.Utilities;
using System.Threading;
using System.Diagnostics;

namespace MyPhotoIndex.ImageBank.Query
{
    public class QueryBuilder
    {
        private QueryParameter[] m_queryParams;
        private IImageCollection m_imageList;
        private bool m_filterVersions;
        private Dictionary<String, PropertyInfo> reflectionCache = new Dictionary<string, PropertyInfo>();
        private int m_matchAndConds = 0;
        Dictionary<int, IImageData> m_filteredList = new Dictionary<int, IImageData>();


        public QueryBuilder(QueryParameter[] queryParameters, IImageCollection imageList, bool filterImageVersions)
        {
            m_queryParams = queryParameters;
            m_imageList = imageList;
            m_filterVersions = filterImageVersions;
        }

        public Dictionary<int, IImageData> RunQuery()
        {
            m_filteredList.Clear();
            if (m_imageList.Count == 0)
            {
                return m_filteredList;
            }

            m_matchAndConds = 0;
            foreach (QueryParameter qp in m_queryParams)
            {
                if (qp.Relation == QueryParameterRelation.And)
                {
                    m_matchAndConds++;
                }
            }

            IImageData[] imageArray = m_imageList.ToArray();
            int count = imageArray.Length;

            //take advantage of multi core CPU... everyone have them these days :)
            int numOfCores = Environment.ProcessorCount;
            if (numOfCores == 1)
            {
                int imageIndex = 0;
                GetFilteredList(imageArray, imageIndex, count);
            }
            else
            {
                Parallel.ForEach<IImageData>(imageArray, new Action<IImageData>(EvaluateImage));
            }

            return m_filteredList;
        }

        private void GetFilteredList(IImageData[] imageArray, int from, int to)
        {
            Dictionary<int, IImageData> filteredList = new Dictionary<int, IImageData>();

            for (int imageIndex = from; imageIndex < to; imageIndex++)
            {
                IImageData imageData = imageArray[imageIndex];
                EvaluateImage(imageData);
            }
        }

        private void EvaluateImage(IImageData imageData)
        {
            bool match = IsImageMach(imageData);
            if (match == true)
            {
                System.Diagnostics.Debug.Assert(imageData.Id != 0);
                lock (m_filteredList)
                {
                    m_filteredList.Add(imageData.Id, imageData);
                }
            }
        }

        private bool IsImageMach(IImageData imageData)
        {
            Type type = typeof(IImageData);

            //Show different versions of the same image.....
            if (imageData.VersionParentId > -1 &&
                m_filterVersions == true)
            {
                return false;
            }

            int match = m_matchAndConds;
            int matchOrConds = 1;

            int paramCount = m_queryParams.Length;
            for (int paramIndex = 0; paramIndex < paramCount; paramIndex++)
            {
                QueryParameter qp = m_queryParams[paramIndex];

                PropertyInfo propInfo = null;
                if (reflectionCache.TryGetValue(qp.PropertyPath, out propInfo) == false)
                {
                    lock (reflectionCache)
                    {
                        if (reflectionCache.TryGetValue(qp.PropertyPath, out propInfo) == false)
                        {
                            propInfo = type.GetProperty(qp.PropertyPath);
                            reflectionCache.Add(qp.PropertyPath, propInfo);
                        }
                    }
                }

                Object propValue = propInfo.GetValue(imageData, null);
                if (propValue != null)
                {
                    if (EvaluateParameter(propValue, qp, reflectionCache) == true)
                    {
                        if (qp.Relation == QueryParameterRelation.And)
                        {
                            match--;
                        }
                        else
                        {
                            matchOrConds = 0;
                        }
                    }

                    if ((m_matchAndConds > 0 && 0 == match)
                        || 0 == matchOrConds)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private static bool EvaluateParameter(Object propertyValue, QueryParameter queryParameter, Dictionary<String, PropertyInfo> reflectionCache)
        {
            PropertyInfo countProperty = null;
            String path = String.Concat(queryParameter.PropertyPath, "\\Count");
            if (reflectionCache.TryGetValue(path, out countProperty) == false)
            {
                lock (reflectionCache)
                {
                    if (reflectionCache.TryGetValue(path, out countProperty) == false)
                    {
                        countProperty = propertyValue.GetType().GetProperty("Count");
                        reflectionCache.Add(path, countProperty);
                    }
                }
            }

            if (countProperty == null ||
                queryParameter.CompareParent == true)
            {
                return EvaluateParameter(propertyValue, queryParameter);
            }

            int numInArray = (int)countProperty.GetValue(propertyValue, null);

            PropertyInfo arrayIndexer = null;
            String itemPath = String.Concat(queryParameter.PropertyPath, "\\Item");
            if (reflectionCache.TryGetValue(itemPath, out arrayIndexer) == false)
            {
                lock (reflectionCache)
                {
                    if (reflectionCache.TryGetValue(itemPath, out arrayIndexer) == false)
                    {
                        arrayIndexer = propertyValue.GetType().GetProperty("Item");
                        reflectionCache.Add(itemPath, arrayIndexer);
                    }
                }
            }

            int neCount = numInArray;
            for (int ii = 0; ii < numInArray; ii++)
            {
                Object value = arrayIndexer.GetValue(propertyValue, new Object[] { ii });
                if (EvaluateParameter(value, queryParameter) == true)
                {
                    if (queryParameter.Operation != QueryParameterOperation.NotEqual)
                    {
                        return true;
                    }
                    else
                    {
                        neCount--;
                    }
                }
            }

            return (numInArray > 0 && neCount == 0) ? true : false;
        }

        private static bool EvaluateParameter(object value, QueryParameter queryParameter)
        {
            switch (queryParameter.Operation)
            {
                case QueryParameterOperation.Equal:
                    if (queryParameter.Equals(value))
                    {
                        return true;
                    }
                    break;
                case QueryParameterOperation.NotEqual:
                    if (queryParameter.Equals(value) == false)
                    {
                        return true;
                    }
                    break;
                case QueryParameterOperation.GreaterThen:
                    if (queryParameter.GreaterThen(value))
                    {
                        return true;
                    }
                    break;
                case QueryParameterOperation.LessThen:
                    if (queryParameter.LessThen(value))
                    {
                        return true;
                    }
                    break;
                case QueryParameterOperation.LessThenEquals:
                    if (queryParameter.LessThenEquals(value))
                    {
                        return true;
                    }
                    break;
                default:
                    break;
            }

            return false;
        }
    }
}
